## Замыкания

функции можно объявлять внутри других функций. Например, так:
```python
def say_name(name):
    def say_goodbye():
        print("Don't say me goodbye, " + name + "!")
 
    say_goodbye()
```
А, затем вызвать:

```python
say_name("Sergey")
```

Мы здесь сначала вызываем внешнюю функцию, затем, в ней формируется вложенная функция say_goodbye() и вызывается.

В результате, в консоли видим сообщение «Don't say me goodbye, Sergey!».

Я, думаю, здесь вам все должно быть понятно.

А теперь сделаем, следующее. Вместо вызова внутренней функции возвратим ссылку на нее с помощью оператора return:
```python
def say_name(name):
    def say_goodbye():
        print("Don't say me goodbye, " + name + "!")
 
    return say_goodbye
```

Конечно, после запуска программы мы не увидим никакого сообщения, так как внутренняя функция нигде не вызывается.

Исправим это.

Сохраним ссылку на функцию say_goodbye() в переменной f:
```python
f = say_name("Sergey")
```
А, затем, вызовем ее:
```python
f()
```

Мы снова видим то же самое сообщение. 

Например, откуда функция `say_goodbye()` берет значение переменной `name`? 
 
Ведь внешняя функция `say_name()` выполнилась и завершилась, а значит, все ее локальные переменные вроде как тоже должны были бы исчезнуть? 
 
 Но нет, мы обращаемся к переменной name и успешно получаем ее значение! 
 
Почему? Давайте разберемся.

Дело в том, что когда у нас имеется глобальная ссылка f на внутреннее, локальное окружение функции `say_goodbye()`, то это окружение продолжает существовать, оно не удаляется автоматически сборщиком мусора, именно из-за этой глобальной ссылки на него. 

А вместе с ним, продолжают существовать и все внешние локальные окружения, в данном случае – окружение функции `say_name()`, потому что также существует неявная, скрытая ссылка на него из внутреннего окружения. 

Такие ссылки формируются автоматически и позволяют, в частности, обращаться к переменным, объявленным в этих внешних окружениях. 

Именно поэтому функция `print()` в `say_goodbye()` имеет доступ к переменной name и эта переменная продолжает существовать, пока существует окружение `say_goodbye`, а значит и окружение `say_name`.

Вот такой эффект, когда мы «держим» внутреннее локальное окружение и имеем возможность продолжать использовать переменные из внешних окружений, в программировании называется замыканием. 

Замыкание в том смысле, что мы держим внутреннее окружение `say_goodbye` переменной `f` из глобального окружения. 

Получается цепочка ссылок, замыкающаяся на глобальном окружении.

Мало того, при каждом новом вызове внешней функции, формируется свое новое, независимое локальное окружение, со своими локальными переменными и соответствующими значениями:

```python
f = say_name("Sergey")
f2 = say_name("Python")
f()
f2()
```

Где может пригодиться такой функционал? Например, можно создать функцию-счетчик, которая бы увеличивала значение локальной переменной на единицу при каждом запуске:

```python
def counter(start=0):
    def step():
        nonlocal start
        start += 1
        return start
 
    return step
```

Обратите внимание, мы здесь используем ключевое слово nonlocal, чтобы переменная start изменялась во внешней локальной области, а не создавалась бы в текущей, локальной.

Без этой строчки возникнет ошибка, из-за неопределенности: мы берем текущее значение start извне, а потом создавали бы переменную с тем же именем внутри области step. 

Так делать нельзя. 
И строчка nonlocal start четко указывает брать переменную start из внешней локальной области, а не создавать в текущей.

Теперь можно сформировать несколько таких независимых счетчиков и выполнить их:

```python
c1 = counter(10)
c2 = counter()
print(c1(), c2())
print(c1(), c2())
print(c1(), c2())
```

У нас, действительно, оба счетчика отработают независимо друг от друга.

И приведу еще один пример с замыканиями. 

Предположим, мы хотим сделать функцию, которая бы удаляла ненужные символы в начале и конце строки.

Через замыкание это можно реализовать, следующим образом:

```python
def strip_string(strip_chars=" "):
    def do_strip(string):
        return string.strip(strip_chars) 
    return do_strip
```

Обратите внимание, вложенная функция тоже имеет параметр – строку, у которой будут удаляться ненужные символы. Далее, создадим два объекта с разным списком удаляемых символов:
```python
strip1 = strip_string()
strip2 = strip_string(" !?,.;")
```

И вызовем вложенную функцию с одним аргументом:

```python
print(strip1(" hello python!.. "))
print(strip2(" hello python!.. "))
```
Смотрите, первая функция `strip1` убрала только пробелы, а вторая еще и восклицательный знак с точками. 

Таким образом, мы можем многократно использовать в программе функции `strip1` и `strip2`, передавая им разные строки.

Вот, что из себя представляют замыкания и вот так они работают. 